package expression;

import java.util.ArrayList;

import runtime.TypeFactory;
import util.Result;
import util.Scope;
import util.Type;
import exception.ExpressionException;
import exception.TypeException;

/**
 * Klasa odpowiedzialna za przetrzymywanie wyrazenia multiplikatywnego
 * 
 * @author Jakub Fibinger
 *
 */
public class MultiplicativeExpression extends Expression {

	public static enum Sign {
		MULTIPLICATION, DIVISION;
	}

	/**
	 * Ciag znakow multiplikatywnych
	 */
	private ArrayList<Sign> signs;
	/**
	 * Ciag wyrazen
	 */
	private ArrayList<Expression> expressions;

	/**
	 * @param scope Zakres zmiennych
	 * @param expression Wyrazenie nizszego poziomu
	 * @throws ExpressionException
	 */
	public MultiplicativeExpression(Scope scope, Expression expression)
			throws ExpressionException {
		if (expression == null) {
			throw new ExpressionException();
		}
//		TypeFactory tf = TypeFactory.getInstance();
		Type type;
		type = expression.getResultType();
//		try {
//			if (!(type.equals(tf.getType("int"))
//					|| type.equals(tf.getType("double"))) ) {
//				throw new ExpressionException();
//			}
//		} catch (TypeException e) {
//			//olać
//		}
		this.expressions = new ArrayList<Expression>();
		this.expressions.add(expression);
		this.signs = new ArrayList<Sign>();
		this.result = new Result(type, null);
	}

	/**
	 * @param scope Zakres zmiennych
	 * @param expressions Ciag wyrazen nizszego poziomu
	 * @param signs Ciag znakow multiplikatywnych
	 * @throws ExpressionException
	 */
	public MultiplicativeExpression(Scope scope, ArrayList<Expression> expressions,
			ArrayList<expression.MultiplicativeExpression.Sign> signs)
			throws ExpressionException {
		TypeFactory tf = TypeFactory.getInstance();
		if ((expressions == null) || (expressions.size() == 0)) {
			throw new ExpressionException();
		} else if (expressions.size() != signs.size() + 1) {
			throw new ExpressionException();
		}
		Type type = expressions.get(0).getResultType();
		try {
			if(expressions.size()>1) {
				for (Expression expr : expressions) {
					Type tmpType = expr.getResultType();
					if ( tmpType.equals(tf.getType("string"))
							|| tmpType.equals(tf.getType("char"))
							|| tmpType.equals(tf.getType("bool")) ) {
						throw new ExpressionException();
					} else if ( tmpType.equals(tf.getType("double")) ) {
						type = tf.getType("double");
					}
				}
			
				Sign div = Sign.DIVISION;
				for(Sign sign : signs) {
					if(sign.compareTo(div)==0) {
						type = tf.getType("double");
						break;
					}
				}
			}

			this.signs = signs;
			this.expressions = expressions;
			result = new Result(type, null);
		} catch (TypeException e) {
			// olac
		}
	}

	/**
	 * Metoda dodajaca kolejne wyrazenie wraz ze znakiem multiplikatywnym
	 * 
	 * @param expression Nowe wyrazenie nizszego poziomu
	 * @param sign Nowy znak multiplikatywny
	 * @throws ExpressionException
	 */
	public void addExpression(Expression expression, Sign sign)
			throws ExpressionException {
		if (expression == null || sign == null) {
			throw new ExpressionException();
		}
		Type type = this.getResultType();
		TypeFactory tf = TypeFactory.getInstance();
		try {
			if(this.expressions.size()>1) {
				Type tmpType = expression.getResultType();
				if (tmpType.equals(tf.getType("string"))
						|| tmpType.equals(tf.getType("char"))
						|| tmpType.equals(tf.getType("bool"))) {
					throw new ExpressionException();
				}
				
				if(sign.compareTo(Sign.DIVISION)==0 || expression.getResultType().equals(tf.getType("double"))){
					type = tf.getType("double");
				}
			}
			signs.add(sign);
			expressions.add(expression);
			this.result.setType(type);
		} catch (TypeException e) {
			// zignoruj
		}
	}

	/* (non-Javadoc)
	 * @see expression.Expression#evaluate()
	 */
	@Override
	public Result evaluate() throws ExpressionException {
		TypeFactory tf = TypeFactory.getInstance();
		
		try {
			if(this.expressions.size()>1) {
				if(this.getResultType().equals(tf.getType("double"))) {
					Expression tmp = this.expressions.get(0);
					Double tmpVal;
					if(tmp.getResultType().equals(tf.getType("int"))) {
						tmpVal = ((Integer)tmp.evaluate().getValue()).doubleValue();
					} else {
						tmpVal = ((Double)tmp.evaluate().getValue());
					}
					for(int i=0; i<signs.size();i++) {
						if(signs.get(i).equals(Sign.MULTIPLICATION)) {
							if(expressions.get(i+1).getResultType().equals(tf.getType("double"))) {
								tmpVal = tmpVal*((Double) expressions.get(i+1).evaluate().getValue());
							} else {
								tmpVal = tmpVal*((Integer) expressions.get(i+1).evaluate().getValue());
							}
						} else {
							if(expressions.get(i+1).getResultType().equals(tf.getType("double"))) {
								tmpVal = tmpVal/((Double) expressions.get(i+1).evaluate().getValue());
							} else {
								tmpVal = tmpVal/((Integer) expressions.get(i+1).evaluate().getValue());
							}
						}
					}
					this.result.setValue(tmpVal);
				} else {
					Expression tmp = this.expressions.get(0);
					Integer tmpVal;
					tmpVal = (Integer)tmp.evaluate().getValue();
					for(int i=0; i<signs.size();i++) {
						tmpVal = tmpVal*((Integer) expressions.get(i+1).evaluate().getValue());
					}
					this.result.setValue(tmpVal);
				}
				
			} else {
				 result = expressions.get(0).evaluate();
			}
		} catch (TypeException e) {
			//olac
		}
		
		return result;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString(){
		StringBuilder sb = new StringBuilder("Multiplicative [ ");
		sb.append(expressions.get(0) + " ");
		int i = 1;
		for(Sign sign : signs)
			sb.append(sign + " " + expressions.get(i++) + " ");
		sb.append("]");
		return sb.toString();
	}

}
